/* Copyright (C) 2012  	Mathieu Marleix <mathieu.marleix@aerian.fr>
 *						Doris Vaton <doris.vaton@gmail.com>
 * 
 * Based on a file of Takashi Kanamaru <kanamaru@cc.kogakuin.ac.jp>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.shaipicku.mjpegcams.mjpegView;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Properties;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

/**
 * Get the Mjpeg stream from an IP camera.
 * @author Mathieu Marleix <mathieu.marleix@aerian.fr>
 * @author Doris Vaton <doris.vaton@gmail.com>
 *
 */
public class MjpegInputStream extends DataInputStream {
	private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 };
	private final byte[] EOF_MARKER = { (byte) 0xFF, (byte) 0xD9 };
	private final String CONTENT_LENGTH = "Content-Length";
	private final static int HEADER_MAX_LENGTH = 100;
	private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
	private boolean DEBUG = false;
	public static final String TAG="IS";
	private int mContentLength = -1;
	byte[] header = null;
	byte[] frameData = null;
	int headerLen = -1;
	int headerLenPrev = -1;
	/**
	 * 
	 * @param in
	 */
	public MjpegInputStream(InputStream in) {
		super(new BufferedInputStream(in, FRAME_MAX_LENGTH));
	}
	public MjpegInputStream(InputStream in, boolean DEBUG) {
		super(new BufferedInputStream(in, FRAME_MAX_LENGTH));
		this.DEBUG = DEBUG;
	}
	public static MjpegInputStream read(String surl) {
		try {
			URL url = new URL(surl);
			HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
			return new MjpegInputStream(urlConnection.getInputStream());
		}catch(Exception e){
			Log.e(TAG, "read: " +  e.getMessage());
		}
		return null;
	}
	/**
	 * Return the end position of a byte sequence given or -1 if the sequence if null.
	 * @param in InputStream, the InputStream which contains the sequence.
	 * @param sequence byte[], the sequence given.
	 * @return int, the end position of the sequence in the InpuStream.
	 * @throws IOException If the read fail.
	 */
	private int getEndOfSequence(DataInputStream in, byte[] sequence) throws IOException {
		int seqIndex = 0;
		byte c;
		for(int i=0; i < FRAME_MAX_LENGTH; i++) {
			c = (byte) in.readUnsignedByte();
			if(c == sequence[seqIndex]) {
				seqIndex++;
				if(seqIndex == sequence.length){
					return i + 1;
				}
			} else seqIndex = 0;
		}
		return -1;
	}
	/**
	 * Return the start position of a byte sequence given or -1 if the sequence if null.
	 * @param in InputStream, the InputStream which contains the sequence.
	 * @param sequence byte[], the sequence given.
	 * @return int, the start position of the sequence in the InpuStream.
	 * @throws IOException If the read in getEndOfSequence fail.
	 */
	private int getStartOfSequence(DataInputStream in, byte[] sequence) throws IOException {
		int end = getEndOfSequence(in, sequence);
		return (end < 0) ? (-1) : (end - sequence.length);
	}
	/**
	 * Return the length of the content in the header of an HTML page.
	 * @param headerBytes the data of the HTML page.
	 * @return	the length of the content.
	 * @throws IOException If the read fail.
	 * @throws NumberFormatException If the number was not recognized.
	 */
	private int parseContentLength(byte[] headerBytes) throws IOException, NumberFormatException {
		ByteArrayInputStream headerIn = new ByteArrayInputStream(headerBytes);
		Properties props = new Properties();
		props.load(headerIn);
		return Integer.parseInt(props.getProperty(CONTENT_LENGTH));
	}	
	/**
	 * Read the Mjpeg stream obtained by the HTTP connection.
	 * @return Bitmap, the resulting decoded bitmap.s
	 * @throws IOException
	 */
	public Bitmap readMjpegFrame() throws IOException {
		mark(FRAME_MAX_LENGTH);
		int headerLen = getStartOfSequence(this, SOI_MARKER);
		reset();

		if(header==null || headerLen != headerLenPrev){
			header = new byte[headerLen];
			if(DEBUG) Log.d("TAG","header renewed");
		}
		headerLenPrev = headerLen;
		readFully(header);

		try {
			mContentLength = parseContentLength(header);
		} catch (NumberFormatException nfe) { 
			mContentLength = getEndOfSequence(this, EOF_MARKER); 
		}
		reset();

		if(frameData==null){
			frameData = new byte[FRAME_MAX_LENGTH*2];
			if(DEBUG) Log.d("TAG","frameData renewed cl="+mContentLength);
		}
		if( mContentLength>FRAME_MAX_LENGTH*2){
			frameData = new byte[mContentLength];
			if(DEBUG) Log.d("TAG","frameData renewed cl="+mContentLength);
		}

		skipBytes(headerLen);

		readFully(frameData, 0, mContentLength);

		return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData,0,mContentLength));
	}
	/**
	 * @return the dEBUG
	 */
	public boolean isDEBUG() {
		return DEBUG;
	}
	/**
	 * @param dEBUG the dEBUG to set
	 */
	public void setDEBUG(boolean dEBUG) {
		DEBUG = dEBUG;
	}
}
